2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../jucer_Headers.h"
27 #include "jucer_PaintElementPath.h"
28 #include "../../properties/jucer_PositionPropertyBase.h"
29 #include "jucer_PaintElementUndoableAction.h"
32 //==============================================================================
33 class ChangePointAction
: public PaintElementUndoableAction
<PaintElementPath
>
36 ChangePointAction (PathPoint
* const point
,
38 const PathPoint
& newValue_
)
39 : PaintElementUndoableAction
<PaintElementPath
> (point
->owner
),
46 ChangePointAction (PathPoint
* const point
,
47 const PathPoint
& newValue_
)
48 : PaintElementUndoableAction
<PaintElementPath
> (point
->owner
),
49 index (point
->owner
->indexOfPoint (point
)),
57 return changeTo (newValue
);
62 return changeTo (oldValue
);
67 PathPoint newValue
, oldValue
;
69 PathPoint
* getPoint() const
71 PathPoint
* p
= getElement()->getPoint (index
);
76 bool changeTo (const PathPoint
& value
) const
80 PaintElementPath
* const path
= getElement();
83 PathPoint
* const p
= path
->getPoint (index
);
86 const bool typeChanged
= (p
->type
!= value
.type
);
91 path
->pointListChanged();
99 //==============================================================================
100 class PathWindingModeProperty
: public ChoicePropertyComponent
,
101 public ChangeListener
104 PathWindingModeProperty (PaintElementPath
* const owner_
)
105 : ChoicePropertyComponent ("winding rule"),
108 choices
.add ("Non-zero winding");
109 choices
.add ("Even/odd winding");
111 owner
->getDocument()->addChangeListener (this);
114 ~PathWindingModeProperty()
116 owner
->getDocument()->removeChangeListener (this);
119 //==============================================================================
120 void setIndex (int newIndex
) { owner
->setNonZeroWinding (newIndex
== 0, true); }
121 int getIndex() const { return owner
->isNonZeroWinding() ? 0 : 1; }
123 void changeListenerCallback (ChangeBroadcaster
*) { refresh(); }
126 PaintElementPath
* const owner
;
130 //==============================================================================
131 PaintElementPath::PaintElementPath (PaintRoutine
* owner
)
132 : ColouredElement (owner
, "Path", true, true),
133 nonZeroWinding (true)
137 PaintElementPath::~PaintElementPath()
141 //==============================================================================
142 static int randomPos (int size
)
144 return size
/ 4 + Random::getSystemRandom().nextInt (size
/ 4) - size
/ 8;
147 void PaintElementPath::setInitialBounds (int w
, int h
)
151 int x
= randomPos (w
);
152 int y
= randomPos (h
);
155 << x
<< " " << y
<< " l "
156 << (x
+ 30) << " " << (y
+ 50) << " l "
157 << (x
- 30) << " " << (y
+ 50) << " x";
159 restorePathFromString (s
);
162 //==============================================================================
163 int PaintElementPath::getBorderSize() const
165 return isStrokePresent
? 1 + roundFloatToInt (strokeType
.stroke
.getStrokeThickness())
169 const Rectangle
<int> PaintElementPath::getCurrentBounds (const Rectangle
<int>& parentArea
) const
171 updateStoredPath (getDocument()->getComponentLayout(), parentArea
);
173 Rectangle
<float> bounds (path
.getBounds());
175 const int borderSize
= getBorderSize();
177 return Rectangle
<int> ((int) bounds
.getX() - borderSize
,
178 (int) bounds
.getY() - borderSize
,
179 (int) bounds
.getWidth() + borderSize
* 2,
180 (int) bounds
.getHeight() + borderSize
* 2);
183 void PaintElementPath::setCurrentBounds (const Rectangle
<int>& b
,
184 const Rectangle
<int>& parentArea
,
187 Rectangle
<int> newBounds (b
);
188 newBounds
.setSize (jmax (1, newBounds
.getWidth()),
189 jmax (1, newBounds
.getHeight()));
191 const Rectangle
<int> current (getCurrentBounds (parentArea
));
193 if (newBounds
!= current
)
195 const int borderSize
= getBorderSize();
197 const int dx
= newBounds
.getX() - current
.getX();
198 const int dy
= newBounds
.getY() - current
.getY();
200 const double scaleStartX
= current
.getX() + borderSize
;
201 const double scaleStartY
= current
.getY() + borderSize
;
202 const double scaleX
= (newBounds
.getWidth() - borderSize
* 2) / (double) (current
.getWidth() - borderSize
* 2);
203 const double scaleY
= (newBounds
.getHeight() - borderSize
* 2) / (double) (current
.getHeight() - borderSize
* 2);
205 for (int i
= 0; i
< points
.size(); ++i
)
207 PathPoint
* const destPoint
= points
.getUnchecked(i
);
208 PathPoint
p (*destPoint
);
210 for (int j
= p
.getNumPoints(); --j
>= 0;)
211 rescalePoint (p
.pos
[j
], dx
, dy
,
213 scaleStartX
, scaleStartY
,
216 perform (new ChangePointAction (destPoint
, i
, p
), "Move path");
221 void PaintElementPath::rescalePoint (RelativePositionedRectangle
& pos
, int dx
, int dy
,
222 double scaleX
, double scaleY
,
223 double scaleStartX
, double scaleStartY
,
224 const Rectangle
<int>& parentArea
) const
227 pos
.getRectangleDouble (x
, y
, w
, h
, parentArea
, getDocument()->getComponentLayout());
229 x
= (x
- scaleStartX
) * scaleX
+ scaleStartX
+ dx
;
230 y
= (y
- scaleStartY
) * scaleY
+ scaleStartY
+ dy
;
232 pos
.updateFrom (x
, y
, w
, h
, parentArea
, getDocument()->getComponentLayout());
235 //==============================================================================
236 static void drawArrow (Graphics
& g
, float x1
, float y1
, float x2
, float y2
)
238 g
.drawArrow (Line
<float> (x1
, y1
, (x1
+ x2
) * 0.5f
, (y1
+ y2
) * 0.5f
), 1.0f
, 8.0f
, 10.0f
);
239 g
.drawLine (x1
+ (x2
- x1
) * 0.49f
, y1
+ (y2
- y1
) * 0.49f
, x2
, y2
);
242 void PaintElementPath::draw (Graphics
& g
, const ComponentLayout
* layout
, const Rectangle
<int>& parentArea
)
244 updateStoredPath (layout
, parentArea
);
245 path
.setUsingNonZeroWinding (nonZeroWinding
);
247 fillType
.setFillType (g
, getDocument(), parentArea
);
252 strokeType
.fill
.setFillType (g
, getDocument(), parentArea
);
253 g
.strokePath (path
, getStrokeType().stroke
);
257 void PaintElementPath::drawExtraEditorGraphics (Graphics
& g
, const Rectangle
<int>& relativeTo
)
259 ComponentLayout
* layout
= getDocument()->getComponentLayout();
261 for (int i
= 0; i
< points
.size(); ++i
)
263 PathPoint
* const p
= points
.getUnchecked (i
);
265 const int numPoints
= p
->getNumPoints();
269 if (owner
->getSelectedPoints().isSelected (p
))
271 g
.setColour (Colours::red
);
272 double x1
, y1
, x2
, y2
;
276 positionToXY (p
->pos
[1], x1
, y1
, relativeTo
, layout
);
277 positionToXY (p
->pos
[2], x2
, y2
, relativeTo
, layout
);
278 drawArrow (g
, (float) x1
, (float) y1
, (float) x2
, (float) y2
);
283 positionToXY (p
->pos
[0], x1
, y1
, relativeTo
, layout
);
284 positionToXY (p
->pos
[1], x2
, y2
, relativeTo
, layout
);
285 drawArrow (g
, (float) x1
, (float) y1
, (float) x2
, (float) y2
);
288 positionToXY (p
->pos
[0], x2
, y2
, relativeTo
, layout
);
290 const PathPoint
* const nextPoint
= points
[i
- 1];
294 positionToXY (nextPoint
->pos
[nextPoint
->getNumPoints() - 1], x1
, y1
, relativeTo
, layout
);
295 drawArrow (g
, (float) x1
, (float) y1
, (float) x2
, (float) y2
);
302 void PaintElementPath::resized()
304 ColouredElement::resized();
307 void PaintElementPath::parentSizeChanged()
312 //==============================================================================
313 void PaintElementPath::mouseDown (const MouseEvent
& e
)
315 if (e
.mods
.isPopupMenu() || ! owner
->getSelectedElements().isSelected (this))
316 mouseDownOnSegment
= -1;
318 mouseDownOnSegment
= findSegmentAtXY (getX() + e
.x
, getY() + e
.y
);
320 if (points
[mouseDownOnSegment
] != 0)
322 mouseDownSelectSegmentStatus
= owner
->getSelectedPoints().addToSelectionOnMouseDown (points
[mouseDownOnSegment
], e
.mods
);
326 ColouredElement::mouseDown (e
);
330 void PaintElementPath::mouseDrag (const MouseEvent
& e
)
332 if (mouseDownOnSegment
< 0)
333 ColouredElement::mouseDrag (e
);
336 void PaintElementPath::mouseUp (const MouseEvent
& e
)
338 if (points
[mouseDownOnSegment
] == 0)
340 ColouredElement::mouseUp (e
);
344 owner
->getSelectedPoints().addToSelectionOnMouseUp (points
[mouseDownOnSegment
],
345 e
.mods
, false, mouseDownSelectSegmentStatus
);
349 //==============================================================================
350 void PaintElementPath::changed()
352 ColouredElement::changed();
353 lastPathBounds
= Rectangle
<int>();
356 void PaintElementPath::pointListChanged()
359 siblingComponentsChanged();
362 //==============================================================================
363 void PaintElementPath::getEditableProperties (Array
<PropertyComponent
*>& properties
)
365 properties
.add (new PathWindingModeProperty (this));
366 getColourSpecificProperties (properties
);
369 //==============================================================================
370 static const String
positionToPairOfValues (const RelativePositionedRectangle
& position
,
371 const ComponentLayout
* layout
)
374 positionToCode (position
, layout
, x
, y
, w
, h
);
375 return castToFloat (x
) + ", " + castToFloat (y
);
378 void PaintElementPath::fillInGeneratedCode (GeneratedCode
& code
, String
& paintMethodCode
)
380 if (fillType
.isInvisible() && (strokeType
.isInvisible() || ! isStrokePresent
))
383 const String
pathVariable ("internalPath" + String (code
.getUniqueSuffix()));
385 const ComponentLayout
* layout
= code
.document
->getComponentLayout();
387 code
.privateMemberDeclarations
388 << "Path " << pathVariable
<< ";\n";
391 bool somePointsAreRelative
= false;
393 if (! nonZeroWinding
)
394 r
<< pathVariable
<< ".setUsingNonZeroWinding (false);\n";
396 for (int i
= 0; i
< points
.size(); ++i
)
398 const PathPoint
* const p
= points
.getUnchecked(i
);
402 case Path::Iterator::startNewSubPath
:
403 r
<< pathVariable
<< ".startNewSubPath (" << positionToPairOfValues (p
->pos
[0], layout
) << ");\n";
404 somePointsAreRelative
= somePointsAreRelative
|| ! p
->pos
[0].rect
.isPositionAbsolute();
407 case Path::Iterator::lineTo
:
408 r
<< pathVariable
<< ".lineTo (" << positionToPairOfValues (p
->pos
[0], layout
) << ");\n";
409 somePointsAreRelative
= somePointsAreRelative
|| ! p
->pos
[0].rect
.isPositionAbsolute();
412 case Path::Iterator::quadraticTo
:
413 r
<< pathVariable
<< ".quadraticTo (" << positionToPairOfValues (p
->pos
[0], layout
)
414 << ", " << positionToPairOfValues (p
->pos
[1], layout
) << ");\n";
415 somePointsAreRelative
= somePointsAreRelative
|| ! p
->pos
[0].rect
.isPositionAbsolute();
416 somePointsAreRelative
= somePointsAreRelative
|| ! p
->pos
[1].rect
.isPositionAbsolute();
419 case Path::Iterator::cubicTo
:
420 r
<< pathVariable
<< ".cubicTo (" << positionToPairOfValues (p
->pos
[0], layout
)
421 << ", " << positionToPairOfValues (p
->pos
[1], layout
)
422 << ", " << positionToPairOfValues (p
->pos
[2], layout
) << ");\n";
423 somePointsAreRelative
= somePointsAreRelative
|| ! p
->pos
[0].rect
.isPositionAbsolute();
424 somePointsAreRelative
= somePointsAreRelative
|| ! p
->pos
[1].rect
.isPositionAbsolute();
425 somePointsAreRelative
= somePointsAreRelative
|| ! p
->pos
[2].rect
.isPositionAbsolute();
428 case Path::Iterator::closePath
:
429 r
<< pathVariable
<< ".closeSubPath();\n";
440 if (somePointsAreRelative
)
441 code
.getCallbackCode (String::empty
, "void", "resized()", false)
442 << pathVariable
<< ".clear();\n" << r
;
444 code
.constructorCode
<< r
;
446 if (! fillType
.isInvisible())
448 fillType
.fillInGeneratedCode (code
, paintMethodCode
);
450 paintMethodCode
<< "g.fillPath (" << pathVariable
<< ");\n";
453 if (isStrokePresent
&& ! strokeType
.isInvisible())
457 strokeType
.fill
.fillInGeneratedCode (code
, s
);
458 s
<< "g.strokePath (" << pathVariable
<< ", " << strokeType
.getPathStrokeCode() << ");\n";
460 paintMethodCode
+= s
;
463 paintMethodCode
+= "\n";
466 //==============================================================================
467 XmlElement
* PaintElementPath::createXml() const
469 XmlElement
* e
= new XmlElement (getTagName());
470 position
.applyToXml (*e
);
471 addColourAttributes (e
);
472 e
->setAttribute ("nonZeroWinding", nonZeroWinding
);
474 e
->addTextElement (pathToString());
479 bool PaintElementPath::loadFromXml (const XmlElement
& xml
)
481 if (xml
.hasTagName (getTagName()))
483 position
.restoreFromXml (xml
, position
);
484 loadColourAttributes (xml
);
485 nonZeroWinding
= xml
.getBoolAttribute ("nonZeroWinding", true);
487 restorePathFromString (xml
.getAllSubText().trim());
498 //==============================================================================
499 void PaintElementPath::createSiblingComponents()
501 ColouredElement::createSiblingComponents();
504 for (i
= 0; i
< points
.size(); ++i
)
506 const PathPoint
* const p
= points
.getUnchecked(i
);
510 case Path::Iterator::startNewSubPath
:
511 siblingComponents
.add (new PathPointComponent (this, i
, 0));
513 case Path::Iterator::lineTo
:
514 siblingComponents
.add (new PathPointComponent (this, i
, 0));
516 case Path::Iterator::quadraticTo
:
517 siblingComponents
.add (new PathPointComponent (this, i
, 0));
518 siblingComponents
.add (new PathPointComponent (this, i
, 1));
520 case Path::Iterator::cubicTo
:
521 siblingComponents
.add (new PathPointComponent (this, i
, 0));
522 siblingComponents
.add (new PathPointComponent (this, i
, 1));
523 siblingComponents
.add (new PathPointComponent (this, i
, 2));
525 case Path::Iterator::closePath
:
533 for (i
= 0; i
< siblingComponents
.size(); ++i
)
535 getParentComponent()->addAndMakeVisible (siblingComponents
.getUnchecked(i
));
536 siblingComponents
.getUnchecked(i
)->updatePosition();
541 const String
PaintElementPath::pathToString() const
545 for (int i
= 0; i
< points
.size(); ++i
)
547 const PathPoint
* const p
= points
.getUnchecked(i
);
551 case Path::Iterator::startNewSubPath
:
552 s
<< "s " << positionToString (p
->pos
[0]) << ' ';
554 case Path::Iterator::lineTo
:
555 s
<< "l " << positionToString (p
->pos
[0]) << ' ';
557 case Path::Iterator::quadraticTo
:
558 s
<< "q " << positionToString (p
->pos
[0])
559 << ' ' << positionToString (p
->pos
[1]) << ' ';
561 case Path::Iterator::cubicTo
:
562 s
<< "c " << positionToString (p
->pos
[0])
563 << ' ' << positionToString (p
->pos
[1]) << ' '
564 << ' ' << positionToString (p
->pos
[2]) << ' ';
566 case Path::Iterator::closePath
:
578 void PaintElementPath::restorePathFromString (const String
& s
)
583 tokens
.addTokens (s
, false);
585 tokens
.removeEmptyStrings();
587 for (int i
= 0; i
< tokens
.size(); ++i
)
589 PathPoint
* p
= new PathPoint (this);
591 if (tokens
[i
] == "s")
593 p
->type
= Path::Iterator::startNewSubPath
;
594 p
->pos
[0] = RelativePositionedRectangle();
595 p
->pos
[0].rect
= PositionedRectangle (tokens
[i
+ 1] + " " + tokens
[i
+ 2]);
598 else if (tokens
[i
] == "l")
600 p
->type
= Path::Iterator::lineTo
;
601 p
->pos
[0] = RelativePositionedRectangle();
602 p
->pos
[0].rect
= PositionedRectangle (tokens
[i
+ 1] + " " + tokens
[i
+ 2]);
605 else if (tokens
[i
] == "q")
607 p
->type
= Path::Iterator::quadraticTo
;
608 p
->pos
[0] = RelativePositionedRectangle();
609 p
->pos
[0].rect
= PositionedRectangle (tokens
[i
+ 1] + " " + tokens
[i
+ 2]);
610 p
->pos
[1] = RelativePositionedRectangle();
611 p
->pos
[1].rect
= PositionedRectangle (tokens
[i
+ 3] + " " + tokens
[i
+ 4]);
614 else if (tokens
[i
] == "c")
616 p
->type
= Path::Iterator::cubicTo
;
617 p
->pos
[0] = RelativePositionedRectangle();
618 p
->pos
[0].rect
= PositionedRectangle (tokens
[i
+ 1] + " " + tokens
[i
+ 2]);
619 p
->pos
[1] = RelativePositionedRectangle();
620 p
->pos
[1].rect
= PositionedRectangle (tokens
[i
+ 3] + " " + tokens
[i
+ 4]);
621 p
->pos
[2] = RelativePositionedRectangle();
622 p
->pos
[2].rect
= PositionedRectangle (tokens
[i
+ 5] + " " + tokens
[i
+ 6]);
625 else if (tokens
[i
] == "x")
627 p
->type
= Path::Iterator::closePath
;
639 void PaintElementPath::setToPath (const Path
& p
)
643 Path::Iterator
i (p
);
647 PathPoint
* p
= new PathPoint (this);
648 p
->type
= i
.elementType
;
650 if (i
.elementType
== Path::Iterator::startNewSubPath
)
652 p
->pos
[0].rect
.setX (i
.x1
);
653 p
->pos
[0].rect
.setY (i
.y1
);
655 else if (i
.elementType
== Path::Iterator::lineTo
)
657 p
->pos
[0].rect
.setX (i
.x1
);
658 p
->pos
[0].rect
.setY (i
.y1
);
660 else if (i
.elementType
== Path::Iterator::quadraticTo
)
662 p
->pos
[0].rect
.setX (i
.x1
);
663 p
->pos
[0].rect
.setY (i
.y1
);
664 p
->pos
[1].rect
.setX (i
.x2
);
665 p
->pos
[1].rect
.setY (i
.y2
);
667 else if (i
.elementType
== Path::Iterator::cubicTo
)
669 p
->pos
[0].rect
.setX (i
.x1
);
670 p
->pos
[0].rect
.setY (i
.y1
);
671 p
->pos
[1].rect
.setX (i
.x2
);
672 p
->pos
[1].rect
.setY (i
.y2
);
673 p
->pos
[2].rect
.setX (i
.x3
);
674 p
->pos
[2].rect
.setY (i
.y3
);
676 else if (i
.elementType
== Path::Iterator::closePath
)
689 void PaintElementPath::updateStoredPath (const ComponentLayout
* layout
, const Rectangle
<int>& relativeTo
) const
691 if (lastPathBounds
!= relativeTo
&& ! relativeTo
.isEmpty())
693 lastPathBounds
= relativeTo
;
697 for (int i
= 0; i
< points
.size(); ++i
)
699 const PathPoint
* const p
= points
.getUnchecked(i
);
700 double x1
, y1
, x2
, y2
, x3
, y3
;
704 case Path::Iterator::startNewSubPath
:
705 positionToXY (p
->pos
[0], x1
, y1
, relativeTo
, layout
);
706 path
.startNewSubPath ((float) x1
, (float) y1
);
709 case Path::Iterator::lineTo
:
710 positionToXY (p
->pos
[0], x1
, y1
, relativeTo
, layout
);
711 path
.lineTo ((float) x1
, (float) y1
);
714 case Path::Iterator::quadraticTo
:
715 positionToXY (p
->pos
[0], x1
, y1
, relativeTo
, layout
);
716 positionToXY (p
->pos
[1], x2
, y2
, relativeTo
, layout
);
717 path
.quadraticTo ((float) x1
, (float) y1
, (float) x2
, (float) y2
);
720 case Path::Iterator::cubicTo
:
721 positionToXY (p
->pos
[0], x1
, y1
, relativeTo
, layout
);
722 positionToXY (p
->pos
[1], x2
, y2
, relativeTo
, layout
);
723 positionToXY (p
->pos
[2], x3
, y3
, relativeTo
, layout
);
724 path
.cubicTo ((float) x1
, (float) y1
, (float) x2
, (float) y2
, (float) x3
, (float) y3
);
727 case Path::Iterator::closePath
:
739 //==============================================================================
740 class ChangeWindingAction
: public PaintElementUndoableAction
<PaintElementPath
>
743 ChangeWindingAction (PaintElementPath
* const path
, const bool newValue_
)
744 : PaintElementUndoableAction
<PaintElementPath
> (path
),
745 newValue (newValue_
),
746 oldValue (path
->isNonZeroWinding())
753 getElement()->setNonZeroWinding (newValue
, false);
760 getElement()->setNonZeroWinding (oldValue
, false);
765 bool newValue
, oldValue
;
768 void PaintElementPath::setNonZeroWinding (const bool nonZero
, const bool undoable
)
770 if (nonZero
!= nonZeroWinding
)
774 perform (new ChangeWindingAction (this, nonZero
), "Change path winding rule");
778 nonZeroWinding
= nonZero
;
784 bool PaintElementPath::isSubpathClosed (int index
) const
786 for (int i
= index
+ 1; i
< points
.size(); ++i
)
788 if (points
.getUnchecked (i
)->type
== Path::Iterator::closePath
)
790 else if (points
.getUnchecked (i
)->type
== Path::Iterator::startNewSubPath
)
797 //==============================================================================
798 void PaintElementPath::setSubpathClosed (int index
, const bool closed
, const bool undoable
)
800 if (closed
!= isSubpathClosed (index
))
802 for (int i
= index
+ 1; i
< points
.size(); ++i
)
804 PathPoint
* p
= points
.getUnchecked (i
);
806 if (p
->type
== Path::Iterator::closePath
)
810 deletePoint (i
, undoable
);
813 else if (p
->type
== Path::Iterator::startNewSubPath
)
817 PathPoint
* p
= addPoint (i
- 1, undoable
);
820 p2
.type
= Path::Iterator::closePath
;
821 perform (new ChangePointAction (p
, p2
), "Close subpath");
828 PathPoint
* p
= addPoint (points
.size() - 1, undoable
);
830 p2
.type
= Path::Iterator::closePath
;
831 perform (new ChangePointAction (p
, p2
), "Close subpath");
835 //==============================================================================
836 class AddPointAction
: public PaintElementUndoableAction
<PaintElementPath
>
839 AddPointAction (PaintElementPath
* path
, int pointIndexToAddItAfter_
)
840 : PaintElementUndoableAction
<PaintElementPath
> (path
),
842 pointIndexToAddItAfter (pointIndexToAddItAfter_
)
850 PaintElementPath
* const path
= getElement();
853 PathPoint
* const p
= path
->addPoint (pointIndexToAddItAfter
, false);
856 indexAdded
= path
->indexOfPoint (p
);
857 jassert (indexAdded
>= 0);
865 PaintElementPath
* const path
= getElement();
868 path
->deletePoint (indexAdded
, false);
875 int pointIndexToAddItAfter
;
878 PathPoint
* PaintElementPath::addPoint (int pointIndexToAddItAfter
, const bool undoable
)
882 AddPointAction
* action
= new AddPointAction (this, pointIndexToAddItAfter
);
883 perform (action
, "Add path point");
884 return points
[action
->indexAdded
];
888 double x1
= 20.0, y1
= 20.0, x2
, y2
;
890 ComponentLayout
* layout
= getDocument()->getComponentLayout();
891 const Rectangle
<int> area (((PaintRoutineEditor
*) getParentComponent())->getComponentArea());
893 if (points
[pointIndexToAddItAfter
] != 0)
894 positionToXY (points
[pointIndexToAddItAfter
]->pos
[points
[pointIndexToAddItAfter
]->getNumPoints() - 1], x1
, y1
,
896 else if (points
[0] != 0)
897 positionToXY (points
[0]->pos
[0], x1
, y1
,
903 if (points
[pointIndexToAddItAfter
+ 1] != 0)
905 if (points
[pointIndexToAddItAfter
+ 1]->type
== Path::Iterator::closePath
906 || points
[pointIndexToAddItAfter
+ 1]->type
== Path::Iterator::startNewSubPath
)
908 int i
= pointIndexToAddItAfter
;
910 if (points
[--i
]->type
== Path::Iterator::startNewSubPath
)
913 if (i
!= pointIndexToAddItAfter
)
914 positionToXY (points
[i
]->pos
[0], x2
, y2
,
919 positionToXY (points
[pointIndexToAddItAfter
+ 1]->pos
[0], x2
, y2
,
925 int i
= pointIndexToAddItAfter
+ 1;
927 if (points
[--i
]->type
== Path::Iterator::startNewSubPath
)
930 positionToXY (points
[i
]->pos
[0], x2
, y2
,
934 PathPoint
* const p
= new PathPoint (this);
936 p
->type
= Path::Iterator::lineTo
;
937 p
->pos
[0].rect
.setX ((x1
+ x2
) * 0.5f
);
938 p
->pos
[0].rect
.setY ((y1
+ y2
) * 0.5f
);
940 points
.insert (pointIndexToAddItAfter
+ 1, p
);
948 //==============================================================================
949 class DeletePointAction
: public PaintElementUndoableAction
<PaintElementPath
>
952 DeletePointAction (PaintElementPath
* const path
, const int indexToRemove_
)
953 : PaintElementUndoableAction
<PaintElementPath
> (path
),
954 indexToRemove (indexToRemove_
),
955 oldValue (*path
->getPoint (indexToRemove
))
963 PaintElementPath
* const path
= getElement();
966 path
->deletePoint (indexToRemove
, false);
974 PaintElementPath
* const path
= getElement();
977 PathPoint
* p
= path
->addPoint (indexToRemove
- 1, false);
989 void PaintElementPath::deletePoint (int pointIndex
, const bool undoable
)
993 perform (new DeletePointAction (this, pointIndex
), "Delete path point");
997 PathPoint
* const p
= points
[pointIndex
];
999 if (p
!= 0 && pointIndex
> 0)
1001 owner
->getSelectedPoints().deselect (p
);
1002 owner
->getSelectedPoints().changed (true);
1004 points
.remove (pointIndex
);
1010 //==============================================================================
1011 bool PaintElementPath::getPoint (int index
, int pointNumber
, double& x
, double& y
, const Rectangle
<int>& parentArea
) const
1013 const PathPoint
* const p
= points
[index
];
1018 jassert (pointNumber
< 3 || p
->type
== Path::Iterator::cubicTo
);
1019 jassert (pointNumber
< 2 || p
->type
== Path::Iterator::cubicTo
|| p
->type
== Path::Iterator::quadraticTo
);
1021 positionToXY (p
->pos
[pointNumber
], x
, y
, parentArea
, getDocument()->getComponentLayout());
1025 int PaintElementPath::findSegmentAtXY (int x
, int y
) const
1027 double x1
, y1
, x2
, y2
, x3
, y3
, lastX
= 0.0, lastY
= 0.0, subPathStartX
= 0.0, subPathStartY
= 0.0;
1029 ComponentLayout
* const layout
= getDocument()->getComponentLayout();
1030 const Rectangle
<int> area (((PaintRoutineEditor
*) getParentComponent())->getComponentArea());
1032 int subpathStartIndex
= 0;
1034 float thickness
= 10.0f
;
1035 if (isStrokePresent
)
1036 thickness
= jmax (thickness
, strokeType
.stroke
.getStrokeThickness());
1038 for (int i
= 0; i
< points
.size(); ++i
)
1041 PathPoint
* const p
= points
.getUnchecked (i
);
1045 case Path::Iterator::startNewSubPath
:
1046 positionToXY (p
->pos
[0], lastX
, lastY
, area
, layout
);
1047 subPathStartX
= lastX
;
1048 subPathStartY
= lastY
;
1049 subpathStartIndex
= i
;
1052 case Path::Iterator::lineTo
:
1053 positionToXY (p
->pos
[0], x1
, y1
, area
, layout
);
1055 segmentPath
.addLineSegment (Line
<float> ((float) lastX
, (float) lastY
, (float) x1
, (float) y1
), thickness
);
1056 if (segmentPath
.contains ((float) x
, (float) y
))
1063 case Path::Iterator::quadraticTo
:
1064 positionToXY (p
->pos
[0], x1
, y1
, area
, layout
);
1065 positionToXY (p
->pos
[1], x2
, y2
, area
, layout
);
1067 segmentPath
.startNewSubPath ((float) lastX
, (float) lastY
);
1068 segmentPath
.quadraticTo ((float) x1
, (float) y1
, (float) x2
, (float) y2
);
1069 PathStrokeType (thickness
).createStrokedPath (segmentPath
, segmentPath
);
1071 if (segmentPath
.contains ((float) x
, (float) y
))
1078 case Path::Iterator::cubicTo
:
1079 positionToXY (p
->pos
[0], x1
, y1
, area
, layout
);
1080 positionToXY (p
->pos
[1], x2
, y2
, area
, layout
);
1081 positionToXY (p
->pos
[2], x3
, y3
, area
, layout
);
1083 segmentPath
.startNewSubPath ((float) lastX
, (float) lastY
);
1084 segmentPath
.cubicTo ((float) x1
, (float) y1
, (float) x2
, (float) y2
, (float) x3
, (float) y3
);
1085 PathStrokeType (thickness
).createStrokedPath (segmentPath
, segmentPath
);
1087 if (segmentPath
.contains ((float) x
, (float) y
))
1094 case Path::Iterator::closePath
:
1095 segmentPath
.addLineSegment (Line
<float> ((float) lastX
, (float) lastY
, (float) subPathStartX
, (float) subPathStartY
), thickness
);
1096 if (segmentPath
.contains ((float) x
, (float) y
))
1097 return subpathStartIndex
;
1099 lastX
= subPathStartX
;
1100 lastY
= subPathStartY
;
1112 //==============================================================================
1113 void PaintElementPath::movePoint (int index
, int pointNumber
,
1114 double newX
, double newY
,
1115 const Rectangle
<int>& parentArea
,
1116 const bool undoable
)
1118 PathPoint
* const p
= points
[index
];
1122 PathPoint
newPoint (*p
);
1123 jassert (pointNumber
< 3 || p
->type
== Path::Iterator::cubicTo
);
1124 jassert (pointNumber
< 2 || p
->type
== Path::Iterator::cubicTo
|| p
->type
== Path::Iterator::quadraticTo
);
1126 RelativePositionedRectangle
& pr
= newPoint
.pos
[pointNumber
];
1129 pr
.getRectangleDouble (x
, y
, w
, h
, parentArea
, getDocument()->getComponentLayout());
1130 pr
.updateFrom (newX
, newY
, w
, h
, parentArea
, getDocument()->getComponentLayout());
1134 perform (new ChangePointAction (p
, index
, newPoint
), "Move path point");
1144 const RelativePositionedRectangle
PaintElementPath::getPoint (int index
, int pointNumber
) const
1146 PathPoint
* const p
= points
[index
];
1150 jassert (pointNumber
< 3 || p
->type
== Path::Iterator::cubicTo
);
1151 jassert (pointNumber
< 2 || p
->type
== Path::Iterator::cubicTo
|| p
->type
== Path::Iterator::quadraticTo
);
1153 return p
->pos
[pointNumber
];
1157 return RelativePositionedRectangle();
1160 void PaintElementPath::setPoint (int index
, int pointNumber
, const RelativePositionedRectangle
& newPos
, const bool undoable
)
1162 PathPoint
* const p
= points
[index
];
1166 PathPoint
newPoint (*p
);
1168 jassert (pointNumber
< 3 || p
->type
== Path::Iterator::cubicTo
);
1169 jassert (pointNumber
< 2 || p
->type
== Path::Iterator::cubicTo
|| p
->type
== Path::Iterator::quadraticTo
);
1171 if (newPoint
.pos
[pointNumber
] != newPos
)
1173 newPoint
.pos
[pointNumber
] = newPos
;
1177 perform (new ChangePointAction (p
, index
, newPoint
), "Change path point position");
1192 //==============================================================================
1193 class PathPointTypeProperty
: public ChoicePropertyComponent
,
1194 public ChangeListener
1197 PathPointTypeProperty (PaintElementPath
* const owner_
,
1199 : ChoicePropertyComponent ("point type"),
1203 choices
.add ("Start of sub-path");
1204 choices
.add ("Line");
1205 choices
.add ("Quadratic");
1206 choices
.add ("Cubic");
1208 owner
->getDocument()->addChangeListener (this);
1211 ~PathPointTypeProperty()
1213 owner
->getDocument()->removeChangeListener (this);
1216 //==============================================================================
1217 void setIndex (int newIndex
)
1219 Path::Iterator::PathElementType type
= Path::Iterator::startNewSubPath
;
1224 type
= Path::Iterator::startNewSubPath
;
1228 type
= Path::Iterator::lineTo
;
1232 type
= Path::Iterator::quadraticTo
;
1236 type
= Path::Iterator::cubicTo
;
1244 const Rectangle
<int> area (((PaintRoutineEditor
*) owner
->getParentComponent())->getComponentArea());
1246 owner
->getPoint (index
)->changePointType (type
, area
, true);
1249 int getIndex() const
1251 const PathPoint
* const p
= owner
->getPoint (index
);
1256 case Path::Iterator::startNewSubPath
:
1259 case Path::Iterator::lineTo
:
1262 case Path::Iterator::quadraticTo
:
1265 case Path::Iterator::cubicTo
:
1268 case Path::Iterator::closePath
:
1279 void changeListenerCallback (ChangeBroadcaster
*)
1285 PaintElementPath
* const owner
;
1289 //==============================================================================
1290 class PathPointPositionProperty
: public PositionPropertyBase
1293 PathPointPositionProperty (PaintElementPath
* const owner_
,
1294 const int index_
, const int pointNumber_
,
1296 ComponentPositionDimension dimension_
)
1297 : PositionPropertyBase (owner_
, name
, dimension_
, false, false,
1298 owner_
->getDocument()->getComponentLayout()),
1301 pointNumber (pointNumber_
)
1303 owner
->getDocument()->addChangeListener (this);
1306 ~PathPointPositionProperty()
1308 owner
->getDocument()->removeChangeListener (this);
1311 //==============================================================================
1312 void setPosition (const RelativePositionedRectangle
& newPos
)
1314 owner
->setPoint (index
, pointNumber
, newPos
, true);
1317 const RelativePositionedRectangle
getPosition() const
1319 return owner
->getPoint (index
, pointNumber
);
1323 PaintElementPath
* const owner
;
1324 const int index
, pointNumber
;
1327 //==============================================================================
1328 class PathPointClosedProperty
: public ChoicePropertyComponent
,
1329 private ChangeListener
1332 PathPointClosedProperty (PaintElementPath
* const owner_
, const int index_
)
1333 : ChoicePropertyComponent ("openness"),
1337 owner
->getDocument()->addChangeListener (this);
1339 choices
.add ("Subpath is closed");
1340 choices
.add ("Subpath is open-ended");
1343 ~PathPointClosedProperty()
1345 owner
->getDocument()->removeChangeListener (this);
1348 void changeListenerCallback (ChangeBroadcaster
*)
1353 void setIndex (int newIndex
)
1355 owner
->setSubpathClosed (index
, newIndex
== 0, true);
1358 int getIndex() const
1360 return owner
->isSubpathClosed (index
) ? 0 : 1;
1364 PaintElementPath
* const owner
;
1368 //==============================================================================
1369 class AddNewPointProperty
: public ButtonPropertyComponent
1372 AddNewPointProperty (PaintElementPath
* const owner_
, const int index_
)
1373 : ButtonPropertyComponent ("new point", false),
1379 ~AddNewPointProperty() {}
1381 void buttonClicked()
1383 owner
->addPoint (index
, true);
1386 const String
getButtonText() const { return "Add new point"; }
1389 PaintElementPath
* const owner
;
1394 //==============================================================================
1395 PathPoint::PathPoint (PaintElementPath
* const owner_
)
1400 PathPoint::PathPoint (const PathPoint
& other
)
1401 : owner (other
.owner
),
1404 pos
[0] = other
.pos
[0];
1405 pos
[1] = other
.pos
[1];
1406 pos
[2] = other
.pos
[2];
1409 PathPoint
& PathPoint::operator= (const PathPoint
& other
)
1411 owner
= other
.owner
;
1413 pos
[0] = other
.pos
[0];
1414 pos
[1] = other
.pos
[1];
1415 pos
[2] = other
.pos
[2];
1419 PathPoint::~PathPoint()
1423 int PathPoint::getNumPoints() const
1425 if (type
== Path::Iterator::cubicTo
)
1428 if (type
== Path::Iterator::quadraticTo
)
1431 if (type
== Path::Iterator::closePath
)
1437 const PathPoint
PathPoint::withChangedPointType (const Path::Iterator::PathElementType newType
,
1438 const Rectangle
<int>& parentArea
) const
1440 PathPoint
p (*this);
1442 if (newType
!= p
.type
)
1444 int oldNumPoints
= getNumPoints();
1446 int numPoints
= p
.getNumPoints();
1448 if (numPoints
!= oldNumPoints
)
1450 double lastX
, lastY
;
1453 p
.pos
[numPoints
- 1] = p
.pos
[oldNumPoints
- 1];
1454 p
.pos
[numPoints
- 1].getRectangleDouble (x
, y
, w
, h
, parentArea
, owner
->getDocument()->getComponentLayout());
1456 const int index
= owner
->points
.indexOf (this);
1457 PathPoint
* lastPoint
= owner
->points
[index
- 1];
1459 jassert (lastPoint
!= 0)
1462 lastPoint
->pos
[lastPoint
->getNumPoints() - 1]
1463 .getRectangleDouble (lastX
, lastY
, w
, h
, parentArea
, owner
->getDocument()->getComponentLayout());
1471 for (int i
= 0; i
< numPoints
- 1; ++i
)
1473 p
.pos
[i
] = p
.pos
[numPoints
- 1];
1475 p
.pos
[i
].updateFrom (lastX
+ (x
- lastX
) * (i
+ 1) / numPoints
,
1476 lastY
+ (y
- lastY
) * (i
+ 1) / numPoints
,
1479 owner
->getDocument()->getComponentLayout());
1487 void PathPoint::changePointType (const Path::Iterator::PathElementType newType
,
1488 const Rectangle
<int>& parentArea
, const bool undoable
)
1490 if (newType
!= type
)
1494 owner
->perform (new ChangePointAction (this, withChangedPointType (newType
, parentArea
)),
1495 "Change path point type");
1499 *this = withChangedPointType (newType
, parentArea
);
1500 owner
->pointListChanged();
1505 void PathPoint::getEditableProperties (Array
<PropertyComponent
*>& properties
)
1507 const int index
= owner
->points
.indexOf (this);
1508 jassert (index
>= 0);
1512 case Path::Iterator::startNewSubPath
:
1513 properties
.add (new PathPointPositionProperty (owner
, index
, 0, "x", PositionPropertyBase::componentX
));
1514 properties
.add (new PathPointPositionProperty (owner
, index
, 0, "y", PositionPropertyBase::componentY
));
1516 properties
.add (new PathPointClosedProperty (owner
, index
));
1517 properties
.add (new AddNewPointProperty (owner
, index
));
1520 case Path::Iterator::lineTo
:
1521 properties
.add (new PathPointTypeProperty (owner
, index
));
1522 properties
.add (new PathPointPositionProperty (owner
, index
, 0, "x", PositionPropertyBase::componentX
));
1523 properties
.add (new PathPointPositionProperty (owner
, index
, 0, "y", PositionPropertyBase::componentY
));
1524 properties
.add (new AddNewPointProperty (owner
, index
));
1527 case Path::Iterator::quadraticTo
:
1528 properties
.add (new PathPointTypeProperty (owner
, index
));
1529 properties
.add (new PathPointPositionProperty (owner
, index
, 0, "control pt x", PositionPropertyBase::componentX
));
1530 properties
.add (new PathPointPositionProperty (owner
, index
, 0, "control pt y", PositionPropertyBase::componentY
));
1531 properties
.add (new PathPointPositionProperty (owner
, index
, 1, "x", PositionPropertyBase::componentX
));
1532 properties
.add (new PathPointPositionProperty (owner
, index
, 1, "y", PositionPropertyBase::componentY
));
1533 properties
.add (new AddNewPointProperty (owner
, index
));
1536 case Path::Iterator::cubicTo
:
1537 properties
.add (new PathPointTypeProperty (owner
, index
));
1538 properties
.add (new PathPointPositionProperty (owner
, index
, 0, "control pt1 x", PositionPropertyBase::componentX
));
1539 properties
.add (new PathPointPositionProperty (owner
, index
, 0, "control pt1 y", PositionPropertyBase::componentY
));
1540 properties
.add (new PathPointPositionProperty (owner
, index
, 1, "control pt2 x", PositionPropertyBase::componentX
));
1541 properties
.add (new PathPointPositionProperty (owner
, index
, 1, "control pt2 y", PositionPropertyBase::componentY
));
1542 properties
.add (new PathPointPositionProperty (owner
, index
, 2, "x", PositionPropertyBase::componentX
));
1543 properties
.add (new PathPointPositionProperty (owner
, index
, 2, "y", PositionPropertyBase::componentY
));
1544 properties
.add (new AddNewPointProperty (owner
, index
));
1547 case Path::Iterator::closePath
:
1556 void PathPoint::deleteFromPath()
1558 owner
->deletePoint (owner
->points
.indexOf (this), true);
1561 //==============================================================================
1562 PathPointComponent::PathPointComponent (PaintElementPath
* const path_
,
1564 const int pointNumber_
)
1565 : ElementSiblingComponent (path_
),
1567 routine (path_
->getOwner()),
1569 pointNumber (pointNumber_
),
1573 setRepaintsOnMouseActivity (true);
1575 selected
= routine
->getSelectedPoints().isSelected (path_
->points
[index
]);
1576 routine
->getSelectedPoints().addChangeListener (this);
1579 PathPointComponent::~PathPointComponent()
1581 routine
->getSelectedPoints().removeChangeListener (this);
1584 void PathPointComponent::updatePosition()
1586 const Rectangle
<int> area (((PaintRoutineEditor
*) getParentComponent())->getComponentArea());
1587 jassert (getParentComponent() != 0);
1590 path
->getPoint (index
, pointNumber
, x
, y
, area
);
1592 setCentrePosition (roundToInt (x
),
1596 void PathPointComponent::showPopupMenu()
1600 void PathPointComponent::paint (Graphics
& g
)
1602 if (isMouseOverOrDragging())
1603 g
.fillAll (Colours::red
);
1607 g
.setColour (Colours::red
);
1608 g
.drawRect (0, 0, getWidth(), getHeight());
1611 g
.setColour (Colours::white
);
1612 g
.fillRect (getWidth() / 2 - 3, getHeight() / 2 - 3, 7, 7);
1614 g
.setColour (Colours::black
);
1616 if (pointNumber
< path
->getPoint (index
)->getNumPoints() - 1)
1617 g
.drawRect (getWidth() / 2 - 2, getHeight() / 2 - 2, 5, 5);
1619 g
.fillRect (getWidth() / 2 - 2, getHeight() / 2 - 2, 5, 5);
1622 void PathPointComponent::mouseDown (const MouseEvent
& e
)
1626 if (e
.mods
.isPopupMenu())
1629 return; // this may be deleted now..
1632 dragX
= getX() + getWidth() / 2;
1633 dragY
= getY() + getHeight() / 2;
1635 mouseDownSelectStatus
= routine
->getSelectedPoints().addToSelectionOnMouseDown (path
->points
[index
], e
.mods
);
1637 owner
->getDocument()->getUndoManager().beginNewTransaction();
1640 void PathPointComponent::mouseDrag (const MouseEvent
& e
)
1642 if (! e
.mods
.isPopupMenu())
1644 if (selected
&& ! dragging
)
1645 dragging
= ! e
.mouseWasClicked();
1649 const Rectangle
<int> area (((PaintRoutineEditor
*) getParentComponent())->getComponentArea());
1650 int x
= dragX
+ e
.getDistanceFromDragStartX() - area
.getX();
1651 int y
= dragY
+ e
.getDistanceFromDragStartY() - area
.getY();
1653 JucerDocument
* const document
= owner
->getDocument();
1657 x
= document
->snapPosition (x
);
1658 y
= document
->snapPosition (y
);
1661 owner
->getDocument()->getUndoManager().undoCurrentTransactionOnly();
1662 path
->movePoint (index
, pointNumber
, x
+ area
.getX(), y
+ area
.getY(), area
, true);
1667 void PathPointComponent::mouseUp (const MouseEvent
& e
)
1669 routine
->getSelectedPoints().addToSelectionOnMouseUp (path
->points
[index
],
1671 mouseDownSelectStatus
);
1674 void PathPointComponent::changeListenerCallback (ChangeBroadcaster
* source
)
1676 ElementSiblingComponent::changeListenerCallback (source
);
1678 const bool nowSelected
= routine
->getSelectedPoints().isSelected (path
->points
[index
]);
1680 if (nowSelected
!= selected
)
1682 selected
= nowSelected
;
1685 if (getParentComponent() != 0)
1686 getParentComponent()->repaint();